1 package org.apache.lucene.search;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.io.IOException;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Random;
25
26 import junit.framework.Assert;
27
28 import org.apache.lucene.index.BinaryDocValues;
29 import org.apache.lucene.index.FieldInfo;
30 import org.apache.lucene.index.FieldInfos;
31 import org.apache.lucene.index.Fields;
32 import org.apache.lucene.index.IndexReader;
33 import org.apache.lucene.index.LeafReader;
34 import org.apache.lucene.index.LeafReaderContext;
35 import org.apache.lucene.index.MultiReader;
36 import org.apache.lucene.index.NumericDocValues;
37 import org.apache.lucene.index.SortedDocValues;
38 import org.apache.lucene.index.SortedNumericDocValues;
39 import org.apache.lucene.index.SortedSetDocValues;
40 import org.apache.lucene.index.StoredFieldVisitor;
41 import org.apache.lucene.index.Terms;
42 import org.apache.lucene.search.spans.SpanBoostQuery;
43 import org.apache.lucene.util.Bits;
44 import org.apache.lucene.util.LuceneTestCase;
45
46 import static junit.framework.Assert.assertEquals;
47 import static junit.framework.Assert.assertFalse;
48 import static junit.framework.Assert.assertTrue;
49
50
51
52
53 public class QueryUtils {
54
55
56 public static void check(Query q) {
57 checkHashEquals(q);
58
59 if (q instanceof FilteredQuery) {
60
61
62
63 FilteredQuery filtered = (FilteredQuery) q;
64 check(filtered.getQuery());
65 check(filtered.getFilter());
66 }
67
68 try {
69 IndexSearcher dummySearcher = new IndexSearcher(new MultiReader());
70 Query clone = q.clone();
71 clone.setBoost((float) Math.PI);
72 Query rewritten = dummySearcher.rewrite(clone);
73 Assert.assertTrue("Query " + clone.getClass() + " does not propagate Query.rewrite call to super.rewrite",
74 rewritten instanceof BoostQuery || rewritten instanceof SpanBoostQuery);
75 } catch (IOException ioe) {
76 throw new AssertionError("Unexpected I/O error", ioe);
77 }
78 }
79
80
81 public static void checkHashEquals(Query q) {
82 checkEqual(q,q);
83
84 Query q2 = q.clone();
85 checkEqual(q,q2);
86
87 Query q3 = q.clone();
88 q3.setBoost(7.21792348f);
89 checkUnequal(q,q3);
90
91
92
93 Query whacky = new Query() {
94 @Override
95 public String toString(String field) {
96 return "My Whacky Query";
97 }
98 };
99 checkUnequal(q, whacky);
100
101
102 assertFalse(q.equals(null));
103 }
104
105 public static void checkEqual(Query q1, Query q2) {
106 assertEquals(q1, q2);
107 assertEquals(q1.hashCode(), q2.hashCode());
108 }
109
110 public static void checkUnequal(Query q1, Query q2) {
111 assertFalse(q1 + " equal to " + q2, q1.equals(q2));
112 assertFalse(q2 + " equal to " + q1, q2.equals(q1));
113
114
115
116 assertTrue(q1.hashCode() != q2.hashCode());
117 }
118
119
120 public static void checkExplanations (final Query q, final IndexSearcher s) throws IOException {
121 CheckHits.checkExplanations(q, null, s, true);
122 }
123
124
125
126
127
128
129
130
131
132
133
134 public static void check(Random random, Query q1, IndexSearcher s) {
135 check(random, q1, s, true);
136 }
137 public static void check(Random random, Query q1, IndexSearcher s, boolean wrap) {
138 try {
139 check(q1);
140 if (s!=null) {
141 checkFirstSkipTo(q1,s);
142 checkSkipTo(q1,s);
143 checkBulkScorerSkipTo(random, q1, s);
144 if (wrap) {
145 check(random, q1, wrapUnderlyingReader(random, s, -1), false);
146 check(random, q1, wrapUnderlyingReader(random, s, 0), false);
147 check(random, q1, wrapUnderlyingReader(random, s, +1), false);
148 }
149 checkExplanations(q1,s);
150 }
151 } catch (IOException e) {
152 throw new RuntimeException(e);
153 }
154 }
155
156
157
158
159 public static class FCInvisibleMultiReader extends MultiReader {
160 private final Object cacheKey = new Object();
161
162 public FCInvisibleMultiReader(IndexReader... readers) throws IOException {
163 super(readers);
164 }
165
166 @Override
167 public Object getCoreCacheKey() {
168 return cacheKey;
169 }
170
171 @Override
172 public Object getCombinedCoreAndDeletesKey() {
173 return cacheKey;
174 }
175 }
176
177
178
179
180
181
182
183
184
185
186 public static IndexSearcher wrapUnderlyingReader(Random random, final IndexSearcher s, final int edge)
187 throws IOException {
188
189 IndexReader r = s.getIndexReader();
190
191
192
193 IndexReader[] readers = new IndexReader[] {
194 edge < 0 ? r : new MultiReader(),
195 new MultiReader(),
196 new FCInvisibleMultiReader(edge < 0 ? emptyReader(4) : new MultiReader(),
197 new MultiReader(),
198 0 == edge ? r : new MultiReader()),
199 0 < edge ? new MultiReader() : emptyReader(7),
200 new MultiReader(),
201 new FCInvisibleMultiReader(0 < edge ? new MultiReader() : emptyReader(5),
202 new MultiReader(),
203 0 < edge ? r : new MultiReader())
204 };
205
206 IndexSearcher out = LuceneTestCase.newSearcher(new FCInvisibleMultiReader(readers));
207 out.setSimilarity(s.getSimilarity(true));
208 return out;
209 }
210
211 private static IndexReader emptyReader(final int maxDoc) {
212 return new LeafReader() {
213
214 @Override
215 public void addCoreClosedListener(CoreClosedListener listener) {}
216
217 @Override
218 public void removeCoreClosedListener(CoreClosedListener listener) {}
219
220 @Override
221 public Fields fields() throws IOException {
222 return new Fields() {
223 @Override
224 public Iterator<String> iterator() {
225 return Collections.<String>emptyList().iterator();
226 }
227
228 @Override
229 public Terms terms(String field) throws IOException {
230 return null;
231 }
232
233 @Override
234 public int size() {
235 return 0;
236 }
237 };
238 }
239
240 @Override
241 public NumericDocValues getNumericDocValues(String field) throws IOException {
242 return null;
243 }
244
245 @Override
246 public BinaryDocValues getBinaryDocValues(String field) throws IOException {
247 return null;
248 }
249
250 @Override
251 public SortedDocValues getSortedDocValues(String field) throws IOException {
252 return null;
253 }
254
255 @Override
256 public SortedNumericDocValues getSortedNumericDocValues(String field) throws IOException {
257 return null;
258 }
259
260 @Override
261 public SortedSetDocValues getSortedSetDocValues(String field) throws IOException {
262 return null;
263 }
264
265 @Override
266 public Bits getDocsWithField(String field) throws IOException {
267 return null;
268 }
269
270 @Override
271 public NumericDocValues getNormValues(String field) throws IOException {
272 return null;
273 }
274
275 @Override
276 public FieldInfos getFieldInfos() {
277 return new FieldInfos(new FieldInfo[0]);
278 }
279
280 final Bits liveDocs = new Bits.MatchNoBits(maxDoc);
281 @Override
282 public Bits getLiveDocs() {
283 return liveDocs;
284 }
285
286 @Override
287 public void checkIntegrity() throws IOException {}
288
289 @Override
290 public Fields getTermVectors(int docID) throws IOException {
291 return null;
292 }
293
294 @Override
295 public int numDocs() {
296 return 0;
297 }
298
299 @Override
300 public int maxDoc() {
301 return maxDoc;
302 }
303
304 @Override
305 public void document(int docID, StoredFieldVisitor visitor) throws IOException {}
306
307 @Override
308 protected void doClose() throws IOException {}
309 };
310 }
311
312
313
314
315 public static void checkSkipTo(final Query q, final IndexSearcher s) throws IOException {
316
317 final List<LeafReaderContext> readerContextArray = s.getTopReaderContext().leaves();
318
319 final int skip_op = 0;
320 final int next_op = 1;
321 final int orders [][] = {
322 {next_op},
323 {skip_op},
324 {skip_op, next_op},
325 {next_op, skip_op},
326 {skip_op, skip_op, next_op, next_op},
327 {next_op, next_op, skip_op, skip_op},
328 {skip_op, skip_op, skip_op, next_op, next_op},
329 };
330 for (int k = 0; k < orders.length; k++) {
331
332 final int order[] = orders[k];
333
334
335
336 final int opidx[] = { 0 };
337 final int lastDoc[] = {-1};
338
339
340
341 final float maxDiff = 1e-5f;
342 final LeafReader lastReader[] = {null};
343
344 s.search(q, new SimpleCollector() {
345 private Scorer sc;
346 private Scorer scorer;
347 private int leafPtr;
348
349 @Override
350 public void setScorer(Scorer scorer) {
351 this.sc = scorer;
352 }
353
354 @Override
355 public void collect(int doc) throws IOException {
356 float score = sc.score();
357 lastDoc[0] = doc;
358 try {
359 if (scorer == null) {
360 Weight w = s.createNormalizedWeight(q, true);
361 LeafReaderContext context = readerContextArray.get(leafPtr);
362 scorer = w.scorer(context);
363 }
364
365 int op = order[(opidx[0]++) % order.length];
366
367
368 boolean more = op == skip_op ? scorer.advance(scorer.docID() + 1) != DocIdSetIterator.NO_MORE_DOCS
369 : scorer.nextDoc() != DocIdSetIterator.NO_MORE_DOCS;
370 int scorerDoc = scorer.docID();
371 float scorerScore = scorer.score();
372 float scorerScore2 = scorer.score();
373 float scoreDiff = Math.abs(score - scorerScore);
374 float scorerDiff = Math.abs(scorerScore2 - scorerScore);
375
376 boolean success = false;
377 try {
378 assertTrue(more);
379 assertEquals("scorerDoc=" + scorerDoc + ",doc=" + doc, scorerDoc, doc);
380 assertTrue("score=" + score + ", scorerScore=" + scorerScore, scoreDiff <= maxDiff);
381 assertTrue("scorerScorer=" + scorerScore + ", scorerScore2=" + scorerScore2, scorerDiff <= maxDiff);
382 success = true;
383 } finally {
384 if (!success) {
385 if (LuceneTestCase.VERBOSE) {
386 StringBuilder sbord = new StringBuilder();
387 for (int i = 0; i < order.length; i++) {
388 sbord.append(order[i] == skip_op ? " skip()" : " next()");
389 }
390 System.out.println("ERROR matching docs:" + "\n\t"
391 + (doc != scorerDoc ? "--> " : "") + "doc=" + doc + ", scorerDoc=" + scorerDoc
392 + "\n\t" + (!more ? "--> " : "") + "tscorer.more=" + more
393 + "\n\t" + (scoreDiff > maxDiff ? "--> " : "")
394 + "scorerScore=" + scorerScore + " scoreDiff=" + scoreDiff
395 + " maxDiff=" + maxDiff + "\n\t"
396 + (scorerDiff > maxDiff ? "--> " : "") + "scorerScore2="
397 + scorerScore2 + " scorerDiff=" + scorerDiff
398 + "\n\thitCollector.doc=" + doc + " score=" + score
399 + "\n\t Scorer=" + scorer + "\n\t Query=" + q + " "
400 + q.getClass().getName() + "\n\t Searcher=" + s
401 + "\n\t Order=" + sbord + "\n\t Op="
402 + (op == skip_op ? " skip()" : " next()"));
403 }
404 }
405 }
406 } catch (IOException e) {
407 throw new RuntimeException(e);
408 }
409 }
410
411 @Override
412 public boolean needsScores() {
413 return true;
414 }
415
416 @Override
417 protected void doSetNextReader(LeafReaderContext context) throws IOException {
418
419
420 if (lastReader[0] != null) {
421 final LeafReader previousReader = lastReader[0];
422 IndexSearcher indexSearcher = LuceneTestCase.newSearcher(previousReader);
423 indexSearcher.setSimilarity(s.getSimilarity(true));
424 Weight w = indexSearcher.createNormalizedWeight(q, true);
425 LeafReaderContext ctx = (LeafReaderContext)indexSearcher.getTopReaderContext();
426 Scorer scorer = w.scorer(ctx);
427 if (scorer != null) {
428 boolean more = false;
429 final Bits liveDocs = context.reader().getLiveDocs();
430 for (int d = scorer.advance(lastDoc[0] + 1); d != DocIdSetIterator.NO_MORE_DOCS; d = scorer.nextDoc()) {
431 if (liveDocs == null || liveDocs.get(d)) {
432 more = true;
433 break;
434 }
435 }
436 Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but advance("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
437 }
438 leafPtr++;
439 }
440 lastReader[0] = context.reader();
441 assert readerContextArray.get(leafPtr).reader() == context.reader();
442 this.scorer = null;
443 lastDoc[0] = -1;
444 }
445 });
446
447 if (lastReader[0] != null) {
448
449
450 final LeafReader previousReader = lastReader[0];
451 IndexSearcher indexSearcher = LuceneTestCase.newSearcher(previousReader, false);
452 indexSearcher.setSimilarity(s.getSimilarity(true));
453 Weight w = indexSearcher.createNormalizedWeight(q, true);
454 LeafReaderContext ctx = previousReader.getContext();
455 Scorer scorer = w.scorer(ctx);
456 if (scorer != null) {
457 boolean more = false;
458 final Bits liveDocs = lastReader[0].getLiveDocs();
459 for (int d = scorer.advance(lastDoc[0] + 1); d != DocIdSetIterator.NO_MORE_DOCS; d = scorer.nextDoc()) {
460 if (liveDocs == null || liveDocs.get(d)) {
461 more = true;
462 break;
463 }
464 }
465 Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but advance("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
466 }
467 }
468 }
469 }
470
471
472 public static void checkFirstSkipTo(final Query q, final IndexSearcher s) throws IOException {
473
474 final float maxDiff = 1e-3f;
475 final int lastDoc[] = {-1};
476 final LeafReader lastReader[] = {null};
477 final List<LeafReaderContext> context = s.getTopReaderContext().leaves();
478 s.search(q,new SimpleCollector() {
479 private Scorer scorer;
480 private int leafPtr;
481 @Override
482 public void setScorer(Scorer scorer) {
483 this.scorer = scorer;
484 }
485 @Override
486 public void collect(int doc) throws IOException {
487 float score = scorer.score();
488 try {
489 long startMS = System.currentTimeMillis();
490 for (int i=lastDoc[0]+1; i<=doc; i++) {
491 Weight w = s.createNormalizedWeight(q, true);
492 Scorer scorer = w.scorer(context.get(leafPtr));
493 Assert.assertTrue("query collected "+doc+" but advance("+i+") says no more docs!",scorer.advance(i) != DocIdSetIterator.NO_MORE_DOCS);
494 Assert.assertEquals("query collected "+doc+" but advance("+i+") got to "+scorer.docID(),doc,scorer.docID());
495 float advanceScore = scorer.score();
496 Assert.assertEquals("unstable advance("+i+") score!",advanceScore,scorer.score(),maxDiff);
497 Assert.assertEquals("query assigned doc "+doc+" a score of <"+score+"> but advance("+i+") has <"+advanceScore+">!",score,advanceScore,maxDiff);
498
499
500
501 if (i < doc && System.currentTimeMillis() - startMS > 5) {
502 i = doc-1;
503 }
504 }
505 lastDoc[0] = doc;
506 } catch (IOException e) {
507 throw new RuntimeException(e);
508 }
509 }
510
511 @Override
512 public boolean needsScores() {
513 return true;
514 }
515
516 @Override
517 protected void doSetNextReader(LeafReaderContext context) throws IOException {
518
519
520 if (lastReader[0] != null) {
521 final LeafReader previousReader = lastReader[0];
522 IndexSearcher indexSearcher = LuceneTestCase.newSearcher(previousReader);
523 indexSearcher.setSimilarity(s.getSimilarity(true));
524 Weight w = indexSearcher.createNormalizedWeight(q, true);
525 Scorer scorer = w.scorer((LeafReaderContext)indexSearcher.getTopReaderContext());
526 if (scorer != null) {
527 boolean more = false;
528 final Bits liveDocs = context.reader().getLiveDocs();
529 for (int d = scorer.advance(lastDoc[0] + 1); d != DocIdSetIterator.NO_MORE_DOCS; d = scorer.nextDoc()) {
530 if (liveDocs == null || liveDocs.get(d)) {
531 more = true;
532 break;
533 }
534 }
535 Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but advance("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
536 }
537 leafPtr++;
538 }
539
540 lastReader[0] = context.reader();
541 lastDoc[0] = -1;
542 }
543 });
544
545 if (lastReader[0] != null) {
546
547
548 final LeafReader previousReader = lastReader[0];
549 IndexSearcher indexSearcher = LuceneTestCase.newSearcher(previousReader);
550 indexSearcher.setSimilarity(s.getSimilarity(true));
551 Weight w = indexSearcher.createNormalizedWeight(q, true);
552 Scorer scorer = w.scorer((LeafReaderContext)indexSearcher.getTopReaderContext());
553 if (scorer != null) {
554 boolean more = false;
555 final Bits liveDocs = lastReader[0].getLiveDocs();
556 for (int d = scorer.advance(lastDoc[0] + 1); d != DocIdSetIterator.NO_MORE_DOCS; d = scorer.nextDoc()) {
557 if (liveDocs == null || liveDocs.get(d)) {
558 more = true;
559 break;
560 }
561 }
562 Assert.assertFalse("query's last doc was "+ lastDoc[0] +" but advance("+(lastDoc[0]+1)+") got to "+scorer.docID(),more);
563 }
564 }
565 }
566
567
568 public static void checkBulkScorerSkipTo(Random r, Query query, IndexSearcher searcher) throws IOException {
569 Weight weight = searcher.createNormalizedWeight(query, true);
570 for (LeafReaderContext context : searcher.getIndexReader().leaves()) {
571 final Scorer scorer = weight.scorer(context);
572 final BulkScorer bulkScorer = weight.bulkScorer(context);
573 if (scorer == null && bulkScorer == null) {
574 continue;
575 } else if (bulkScorer == null) {
576
577 assert scorer.nextDoc() == DocIdSetIterator.NO_MORE_DOCS;
578 continue;
579 }
580 int upTo = 0;
581 while (true) {
582 final int min = upTo + r.nextInt(5);
583 final int max = min + 1 + r.nextInt(r.nextBoolean() ? 10 : 5000);
584 if (scorer.docID() < min) {
585 scorer.advance(min);
586 }
587 final int next = bulkScorer.score(new LeafCollector() {
588 Scorer scorer2;
589 @Override
590 public void setScorer(Scorer scorer) throws IOException {
591 this.scorer2 = scorer;
592 }
593 @Override
594 public void collect(int doc) throws IOException {
595 assert doc >= min;
596 assert doc < max;
597 Assert.assertEquals(scorer.docID(), doc);
598 Assert.assertEquals(scorer.score(), scorer2.score(), 0.01f);
599 scorer.nextDoc();
600 }
601 }, null, min, max);
602 assert max <= next;
603 assert next <= scorer.docID();
604 upTo = max;
605
606 if (scorer.docID() == DocIdSetIterator.NO_MORE_DOCS) {
607 bulkScorer.score(new LeafCollector() {
608 @Override
609 public void setScorer(Scorer scorer) throws IOException {}
610
611 @Override
612 public void collect(int doc) throws IOException {
613
614 assert false;
615 }
616 }, null, upTo, DocIdSetIterator.NO_MORE_DOCS);
617 break;
618 }
619 }
620 }
621 }
622 }